package heartbeat;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.CharBuffer;
import java.nio.channels.SocketChannel;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.util.Arrays;

public class TcpConnection 
{
	private static final int HEADER_LENGTH = 4;
	private static final int CHANNEL_LENGTH = 4;
	
	protected boolean mConnected = false;
	protected SocketChannel mSocketChannel;
	public TcpConnection(String address, int port) throws IOException
	{
		mSocketChannel = SocketChannel.open();
		System.out.println("Connecting to: " + address + ":" + port);
		mSocketChannel.connect(new InetSocketAddress(address, port));
		mConnected = true;
		mSocketChannel.configureBlocking(false);
	}
	
	public void write(Packet msg) throws IOException
	{
		String msgStr = msg.message().toString();

		ByteBuffer buf = ByteBuffer.allocate(HEADER_LENGTH + CHANNEL_LENGTH + msgStr.length());
		buf.order(ByteOrder.LITTLE_ENDIAN);
		buf.clear();
		buf.putInt(msgStr.length());
		buf.putInt(msg.channel());
		buf.put(msgStr.getBytes());
		buf.flip();
		writeInternal(buf);
	}
	
	protected void writeInternal(ByteBuffer buf) throws IOException
	{
		while(buf.hasRemaining())
		{
		    int num = mSocketChannel.write(buf);
		    if(num < 0)
		    	throw new IOException("Bad write");
		}
	}
	
	public void close()
	{
		try
		{
			mSocketChannel.close();
		}
		catch(IOException e)
		{
		}
		
		mConnected = false;
	}
	
	public int readInternal(ByteBuffer dst) throws IOException
	{
		return mSocketChannel.read(dst);
	}
	
	public Packet read() throws IOException
	{
		boolean connected = mSocketChannel.isConnected();
		if(!connected)
			throw new IOException();

		int readLength = HEADER_LENGTH + CHANNEL_LENGTH;
		ByteBuffer buf = ByteBuffer.allocate(readLength);
		int bytesRemaining = readLength;
		while(true)
		{
			long bytesRead = readInternal(buf);

			if(bytesRead < 0)
				throw new IOException("End of stream");
			
			if(bytesRead == 0)
				return null;

			bytesRemaining -= bytesRead;
			if(bytesRemaining == 0)
				break;
		}

		byte[] bufArray = buf.array();

		//Split the header into body length and channel.
		byte[] bodyLenByteArr = Arrays.copyOfRange(bufArray, 0, HEADER_LENGTH);
		byte[] channelByteArr = Arrays.copyOfRange(bufArray, HEADER_LENGTH, HEADER_LENGTH + CHANNEL_LENGTH);

		int bodyLength = ByteBuffer.wrap(bodyLenByteArr).order(ByteOrder.LITTLE_ENDIAN).getInt();
		int channel = ByteBuffer.wrap(channelByteArr).order(ByteOrder.LITTLE_ENDIAN).getInt();

		//Now read the body.
		buf = ByteBuffer.allocate(bodyLength);
		bytesRemaining = bodyLength;
		while(true)
		{
			long bytesRead = readInternal(buf);
			bytesRemaining -= bytesRead;
			if(bytesRemaining == 0)
				break;
		}
		
		//Flip the buffer.
		buf.flip();
		String bodyStr = byteBufferToString(buf).trim();
		if(bodyStr == null)
			return null;

		return new Packet(channel, PacketUtils.stringToJSON(bodyStr));
	}
	
	protected String byteBufferToString(ByteBuffer buf)
	{
		Charset charset = Charset.forName( "UTF-8" );  
        CharsetDecoder decoder = charset.newDecoder();
        CharBuffer cb;
		try 
		{
			cb = decoder.decode(buf);
		} 
		catch (CharacterCodingException e) 
		{
			return null;
		}
		
		return cb.toString();
	}
}
